package com.dingwen.treasure.kettle.task.manager;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dingwen.treasure.kettle.entity.TaskInfo;
import com.dingwen.treasure.kettle.enums.TaskStateEnum;
import com.dingwen.treasure.kettle.enums.TaskTypeEnum;
import com.dingwen.treasure.kettle.constant.TaskConstant;
import com.dingwen.treasure.kettle.dto.modify.TaskInfoStateDto;
import com.dingwen.treasure.kettle.exception.TaskException;
import com.dingwen.treasure.kettle.service.TaskInfoService;
import com.dingwen.treasure.kettle.vo.TaskInfoVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.EverythingMatcher;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.Predicate;

/**
 * 任务管理器
 *
 * @author dingwen
 * @date 2022/03/13
 */
@Slf4j
@Component
public class TaskManager {

    /**
     * 调度器
     */
    @Resource
    private Scheduler scheduler;

    /**
     * 全局监听存储日志
     */
    @Resource
    private JobListener taskLogListener;


    /**
     * 定时任务job服务
     */
    @Resource
    private TaskInfoService taskInfoService;


    /**
     * 初始化所有任务
     */
    public void init() {
        // 获取所有启用状态的周期性任务信息
        LambdaQueryWrapper<TaskInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TaskInfo::getTaskState, TaskStateEnum.ENABLE)
                .eq(TaskInfo::getTaskType, TaskTypeEnum.PERIODIC_TASK);
        // 逻辑删除自动实现
        List<TaskInfo> taskInfos = taskInfoService.list(queryWrapper);

        if (CollectionUtil.isEmpty(taskInfos)) {
            log.info("当前没有周期性任务需要执行");
            return;
        }
        // 添加工作任务
        taskInfos.parallelStream().forEach(info -> {
            initJob(info);
            log.info("任务初始化成功，共添加{}个任务", taskInfos.size());
        });
    }


    /**
     * 初始化添加job
     *
     * @param taskInfo 信息
     */
    @SneakyThrows
    public void initJob(TaskInfo taskInfo) {
        // 定时任务执行类字节码
        Class<?> clazz = Class.forName(taskInfo.getFullClassName()).newInstance().getClass();
        deleteJob(taskInfo);
        addJob(clazz, taskInfo, false);
        taskInfoService.updateById(taskInfo);
    }

    /**
     * 添加job
     *
     * @param taskInfo 信息
     */
    @SneakyThrows
    public TaskInfo addJob(TaskInfo taskInfo, boolean isSave) {
        judgeExists(taskInfo, optional -> !optional.isPresent());
        // 定时任务执行类字节码
        Class<?> clazz = Class.forName(taskInfo.getFullClassName()).newInstance().getClass();
        return addJob(clazz, taskInfo, isSave);
    }

    /**
     * 添加job
     *
     * @param clazz    clazz
     * @param taskInfo 信息
     * @param isSave   是否新增task info
     * @return {@link TaskInfo}
     * @throws Exception 异常
     */
    public TaskInfo addJob(Class<?> clazz, TaskInfo taskInfo, boolean isSave) throws Exception {


        // 适配 taskInfo
        taskInfo = adapterTaskInfo(taskInfo);

        if (isSave) {
            taskInfoService.save(taskInfo);
        }

        // 启动调度器
        scheduler.start();

        //构建job信息
        JobDetail jobDetail = JobBuilder.newJob(((Job) clazz.newInstance()).getClass())
                .withIdentity(taskInfo.getJobName(), taskInfo.getJobGroupName())
                .build();

        //表达式调度构建器(即任务执行的时间)
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(taskInfo.getCronExpression());

        //按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder
                .newTrigger()
                .withIdentity(taskInfo.getTriggerName(), taskInfo.getTriggerGroupName())
                .withSchedule(scheduleBuilder).build();

        //构建JobDataMap，写入数据
        JobDataMap jobDataMap = buildJobDataMap(taskInfo);
        jobDetail.getJobDataMap().putAll(jobDataMap);

        scheduler.scheduleJob(jobDetail, trigger);

        // 添加全局监听（实现失败成功计数、日志记录）
        scheduler.getListenerManager()
                .addJobListener(taskLogListener, EverythingMatcher.allJobs());

        log.info("任务：{},cron 表达式:{}, 服务器当前时区:{}，jobDataMap: \n{}",
                taskInfo.getTaskName(),
                taskInfo.getCronExpression(),
                TimeZone.getDefault().getID(),
                JSONUtil.toJsonPrettyStr(jobDataMap)
        );
        return taskInfo;
    }


    /**
     * 适配器任务信息： 当 triggerName 、triggerGroupName 、jobGroupName、jobName 为空是默认设置 code 值
     *
     * @param taskInfo 任务信息
     * @return {@link TaskInfo}
     */
    public TaskInfo adapterTaskInfo(TaskInfo taskInfo) {
        //jobName
        if (StrUtil.isBlank(taskInfo.getJobName())) {
            taskInfo.setJobName(taskInfo.getTaskCode());
        }

        //jobGroupName
        if (StrUtil.isBlank(taskInfo.getJobGroupName())) {
            taskInfo.setJobGroupName(taskInfo.getTaskCode());
        }

        // triggerName
        if (StrUtil.isBlank(taskInfo.getTriggerName())) {
            taskInfo.setTriggerName(taskInfo.getTaskCode());
        }

        //triggerGroupName
        if (StrUtil.isBlank(taskInfo.getTriggerGroupName())) {
            taskInfo.setTriggerGroupName(taskInfo.getTaskCode());
        }
        return taskInfo;

    }

    /**
     * 删除工作: 删除taskInfo
     *
     * @param taskInfo 任务信息
     */
    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    public void deleteJob(TaskInfo taskInfo) {
        TriggerKey triggerKey = buildTriggerKey(taskInfo);
        JobKey jobKey = buildJobKey(taskInfo);
        // 暂停触发器
        scheduler.pauseTrigger(triggerKey);
        // 卸载已装配到调度器的触发器
        scheduler.unscheduleJob(triggerKey);
        // 删除工作任务
        scheduler.deleteJob(jobKey);
        String message = "删除任务：\n{},成功";
        log.info(message, JSONUtil.toJsonPrettyStr(taskInfo));
    }

    /**
     * 修改工作任务
     *
     * @param taskInfo 信息
     */
    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    public void modifyJob(TaskInfo taskInfo) {
        // 判断
        Optional<JobDetail> jobDetailOptional = judgeExists(taskInfo, Optional::isPresent);
        Assert.isTrue(jobDetailOptional.isPresent(), "作业详情不能为空");
        Class<? extends Job> jobClass = jobDetailOptional.get().getJobClass();

        // 删除
        deleteJob(taskInfo);
        //添加任务
        addJob(jobClass, taskInfo, false);
        // 修改taskInfo
        taskInfoService.updateById(taskInfo);
        log.info("修改任务成功：\n{}", JSONUtil.toJsonStr(taskInfo));
    }

    /**
     * 判读是否存在： 修改必须事先存在，新增必须不存在
     *
     * @param taskInfo 任务信息
     * @return {@link Class}<{@link ?} {@link extends} {@link Job}>
     * @throws SchedulerException 计划程序异常
     */
    public Optional<JobDetail> judgeExists(TaskInfo taskInfo, Predicate<Optional> predicate) throws Exception {

        // 适配
        TaskInfo taskInfoForAdapter = adapterTaskInfo((taskInfo));
        TriggerKey triggerKey = buildTriggerKey(taskInfoForAdapter);
        JobKey jobKey = buildJobKey(taskInfoForAdapter);

        Optional<CronTrigger> optionalCronTrigger = Optional.ofNullable((CronTrigger) scheduler.getTrigger(triggerKey));

        Optional<JobDetail> jobDetailOptional = Optional.ofNullable(scheduler.getJobDetail(jobKey));

        if (!(predicate.test(optionalCronTrigger) && predicate.test(jobDetailOptional))) {
            String template = "新增或修改任务信息异常: 触发器：\n{} \n任务：\n{}";
            String message = StrUtil.format(template, JSONUtil.toJsonStr(triggerKey), JSONUtil.toJsonPrettyStr(jobKey));
            log.warn(message);
            throw new TaskException(message);
        }
        return jobDetailOptional;
    }

    /**
     * 现在就开始工作
     * 立即执行任务
     *
     * @param taskId 任务 id
     */
    @SneakyThrows
    public void startJobNow(Long taskId) {
        String template = "taskId: {},立即执行失败，请确保任务处于启用状态";
        String message = StrUtil.format(template, taskId);
        try {
            TaskInfo taskInfo = taskInfoService.getById(taskId);
            Assert.isTrue(taskInfo.getTaskState() == TaskStateEnum.ENABLE, message);
            JobDataMap jobDataMap = buildJobDataMap(taskInfo);
            JobKey jobKey = buildJobKey(taskInfo);
            scheduler.triggerJob(jobKey, jobDataMap);
        } catch (SchedulerException e) {
            throw new TaskException(message);
        }
    }


    /**
     * 获取所有执行中的任务列表
     *
     * @return 结果
     */
    @SneakyThrows
    public List<Map<String, Object>> getAllJob() {
        // 获取所有的JobKey
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        // 获取所有的Job
        List<Map<String, Object>> jobList = new ArrayList<>();

        for (JobKey jobKey : jobKeys) {
            // 获取所有的Trigger
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
            for (Trigger trigger : triggers) {
                Map<String, Object> job = new HashMap<>(16);
                job.put(TaskConstant.JOB_NAME, jobKey.getName());
                job.put(TaskConstant.JOB_GROUP_NAME, jobKey.getGroup());
                job.put(TaskConstant.TRIGGER, trigger.getKey());
                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                job.put(TaskConstant.JOB_STATUS, triggerState.name());
                if (trigger instanceof CronTrigger) {
                    CronTrigger cronTrigger = (CronTrigger) trigger;
                    String cronExpression = cronTrigger.getCronExpression();
                    job.put(TaskConstant.CRON_EXPRESSION, cronExpression);
                }
                jobList.add(job);
            }
        }
        return jobList;
    }

    /**
     * 修改任务状态
     *
     * @param taskInfoStateDTO 任务信息
     */
    @Transactional(rollbackFor = Exception.class)
    public void modifyJobState(TaskInfoStateDto taskInfoStateDTO) {

        Long taskId = taskInfoStateDTO.getTaskId();
        TaskInfo taskInfo = taskInfoService.getById(taskId);

        if (ObjectUtils.isEmpty(taskInfo)) {
            String message = "taskId: {},任务不存在，修改状态失败";
            throw new TaskException(StrUtil.format(message, taskId));
        }

        switch (taskInfoStateDTO.getState()) {
            // 启用
            case ENABLE:
                deleteJob(taskInfo);
                addJob(taskInfo, false);
                break;
            // 禁用
            case FORBIDDEN:
                deleteJob(taskInfo);
                break;
            default:
                break;
        }

        TaskInfo taskInfoForUpdate = TaskInfo.builder().taskId(taskId).taskState(taskInfoStateDTO.getState()).build();
        taskInfoService.updateById(taskInfoForUpdate);

    }


    /**
     * 构建task info
     *
     * @param taskInfo 定时任务 info
     * @return {@link Map}
     */
    private JobDataMap buildJobDataMap(TaskInfo taskInfo) {
        JobDataMap jobDataMap = new JobDataMap();
        String jobDataMapJson = taskInfo.getJobDataMap();
        if (StringUtils.hasText(jobDataMapJson) && JSONUtil.isJson(jobDataMapJson)) {
            jobDataMap.putAll(JSONUtil.toBean(jobDataMapJson, Map.class));
        }
        TaskInfoVo taskInfoVo = taskInfoService.getByTaskId(taskInfo.getTaskId());
        jobDataMap.put(TaskConstant.TASK_ID, taskInfoVo.getTaskId());

        jobDataMap.put(TaskConstant.FULL_FILE_NAME, taskInfoVo.getFileFullName());
        jobDataMap.put(TaskConstant.FILE_TYPE, taskInfoVo.getFileType());
        return jobDataMap;
    }

    /**
     * 建立触发键
     *
     * @param taskInfo 任务信息
     * @return TriggerKey
     */
    @SneakyThrows
    private TriggerKey buildTriggerKey(TaskInfo taskInfo) {
        return TriggerKey.triggerKey(taskInfo.getTriggerName(), taskInfo.getTriggerGroupName());
    }

    /**
     * buildJobKey
     *
     * @param taskInfo 任务信息
     * @return JobKey
     */
    private JobKey buildJobKey(TaskInfo taskInfo) {
        return JobKey.jobKey(taskInfo.getJobName(), taskInfo.getJobGroupName());
    }
}
